package tinsiec;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.net.NetworkInterface;
import java.util.concurrent.BlockingQueue;

public class MulticastModule
        implements NetworkConfig.INetworkConfigListener, Runnable
{
    private MulticastSocket socket;
    private InetAddress multicastAddress;
    private int port;

    private boolean keepOnRunning;


    private BlockingQueue<MessageInfo> recvQueue;
    private BlockingQueue<MessageInfo> sendQueue;

    private Thread recvThread;
    private Thread sendThread;

    /**
     Tworzy nowy moduł multicastowy, który łączy się z grupą o podanym adresie
     i porcie.
     @param host nazwa lub adres grupy multicastowej
     @param port numer portu dla grupy multicastowej
     @param parser parser do podziału otrzymywanych wiadomości
     @throws java.io.IOException
     */
    public MulticastModule(String host, int port) throws IOException
    {
        super();
        
        if(NetworkConfig.getSelectedInterface() == null)
            throw new NullPointerException("Nie ustawiono interfejsu.");

        this.port = port;
        multicastAddress = InetAddress.getByName(host);

        NetworkConfig.addListener(this);
    }

    /**
     Zwraca kolejkę, która jest używana w celu przechowywania odebranych
     wiadomości.
     @return queue
     */
    public BlockingQueue<MessageInfo> getRecvQueue()
    {
        return recvQueue;
    }

    /**
     Ustawia kolejkę, która będzie użyta w celu przechowywania odebranych
     wiadomości.
     @param queue
     */
    public void setRecvQueue(BlockingQueue<MessageInfo> queue)
    {
        this.recvQueue = queue;
    }

    /**
     Zwraca kolejkę, która jest używana w celu przechowywania wiadomości
     do wysłania.
     @return queue
     */
    public BlockingQueue<MessageInfo> getSendQueue()
    {
        return sendQueue;
    }

    /**
     Ustawia kolejkę, która będzie użyta w celu przechowywania wiadomości
     do wysłania.
     @param queue
     */
    public void setSendQueue(BlockingQueue<MessageInfo> queue)
    {
        this.sendQueue = queue;
    }

    /**
     Wysyła wiadomość na grupę multicastową.
     @param message wiadomość do wysłania.
     */
    public void sendMessage(MessageInfo message)
    {
        try
        {
            byte[] data = message.getContent().getBytes("utf-8");
            DatagramPacket dp = new DatagramPacket(data, data.length, multicastAddress, port);
            socket.send(dp);
        }
        catch(UnsupportedEncodingException ex)
        {
            ex.printStackTrace();
        }
        catch(IOException ex)
        {
            ex.printStackTrace();
        }
    }

    /**
     Włącza nasłuchiwanie wiadomości przychodzących.
     */
    public void startListening()
    {
        keepOnRunning = true;
        if(recvThread != null)
            recvThread.interrupt();
        if(sendThread != null)
            sendThread.interrupt();

        recvThread = new Thread(this);
        sendThread = new Thread(new Runnable()
        {
            public void run()
            {
                MessageInfo message;
                while(keepOnRunning)
                {
                    try
                    {
                        message = sendQueue.take();
                        sendMessage(message);
                    }
                    catch(InterruptedException ex)
                    {
                        return;
                    }
                }
            }
        });
        try
        {
            socket = new MulticastSocket(port);
            socket.setTimeToLive(1);
            System.out.println("Listening on " + socket);

            socket.setNetworkInterface(NetworkConfig.getSelectedInterface());
            //socket.setLoopbackMode(true);
            socket.joinGroup(multicastAddress);
            recvThread.start();
            sendThread.start();
        }
        catch(IOException ex)
        {
            ex.printStackTrace();
        }
    }

    /**
     Przerywa nasłuchiwanie przez wywołanie wyjątku w wątku nasłuchującym.
     */
    public void stopListening()
    {
        keepOnRunning = false;
        if(recvThread != null)
            recvThread.interrupt();
        if(sendThread != null)
            sendThread.interrupt();
        recvThread = sendThread = null;

        try
        {
            socket.leaveGroup(multicastAddress);
            socket.close();
        }
        catch(IOException ex)
        {
            ex.printStackTrace();
        }
    }

    @Override
    public void run()
    {
        byte[] buffer = new byte[16384];
        DatagramPacket dp = new DatagramPacket(buffer, buffer.length);

        while(keepOnRunning)
        {
            try
            {
                socket.receive(dp);
//                System.out.println("Message from: " + dp.getAddress().getHostAddress());
                if(dp.getAddress().getHostAddress().equals(NetworkConfig.getLocalIP()))
                    continue;
                recvQueue.add(new MessageInfo(new String(dp.getData(), 0, dp.getLength(), "utf-8"), dp.getAddress().getHostAddress()));
//                dataBuffer.append(new String(dp.getData(), 0, dp.getLength(), "utf-8"));
               // while(true)
                {
//                    ParseInfo pi = parser.parseBuffer(dataBuffer.toString());
/*
                    if(pi.isMessageFound())
                    {
                        String message = dataBuffer.substring(pi.getMessageStart(), pi.getMessageEnd());
                        dataBuffer.delete(pi.getMessageStart(), pi.getMessageEnd());
                        if(d)
                        if(recvQueue != null)
                            recvQueue.add(message);
                    }
                    else
                        break;
 */
                }
            }
            catch(IOException ex)
            {
                //ex.printStackTrace();
            }
        }
    }

    public void onInterfaceChanged(NetworkInterface newInterface)
    {
        stopListening();
        startListening();
    }
}
